/**
 * ServerConnection.java
 * 
 * @author Andrew Moss 
 * 
 * Created on 1 Oct 2010
 *
 * A file containing the ServerConnection class.
 */
package com.mossdev.android.moteapp.comms;

import java.io.*;
import java.net.*;

import com.mossdev.android.moteapp.Config;



/**
 * Handles comms with the server.
 * 
 * @author Andrew Moss
 *
 */
public class ServerConnection {
	
	//Globals
	//--------------------------------
	
	//Config object
	private Config config;
	
	//Connection Details
	private Socket clientsock;
    private DataOutputStream outstream = null;
    private DataInputStream instream = null;
    private BufferedReader instreamreader = null;
    
    //Status variables
    private ServerConnectionState status = ServerConnectionState.DISCONNECTED;
    private String lastservermsg = "";

    //Constructors and Destructors
    //--------------------------------

	/**
	 * Constructs the object and tries to connect to the server.
	 * Raises an exception if there is a problem
	 * 
	 * @param port port of server
	 * @param host address of server
	 */
	public ServerConnection(Config config) {
		this.config = config;
	}
	
	//Send a close connection cmd to the server and destroy our connection.
	protected void finalize() throws Throwable
	{
		disconnect();
	} 
	
	
	//Methods
	//----------------------------------------------
	
	/**
	 * Send a specified string to the server. Will check that connection exists
	 * first and will attempt to create a connection if it has gone.
	 * 
	 * @param cmd The string to send to the server
	 * @throws ServerConnectionException
	 */
	public void sendCommand(String cmd) throws ServerConnectionException {
		
		// Check nothing waiting from server
		try {
			String msg;
			if (instreamreader != null && instreamreader.ready()
					&& (msg = instreamreader.readLine()) != null) {
				setLastservermsg(msg);
				if (msg.equals("close")) // TODO Why can't I use the Constant?
					closeConnection();
			}
		} catch (IOException e) {
			throw new ServerConnectionException("Error reading server msg", e);
		}
		
		//May have discovered the connection was closed above, or it may have been closed before.
		if (getConnectionState() != ServerConnectionState.CONNECTED) {
			connect();
		}
		
		// Send command
		try {
			outstream.writeBytes(cmd);
		} catch (IOException e) {
			throw new ServerConnectionException(
					"Error sending a command to the server", e);
		}

		// Wait for echo
		try {
			String msg;
			if ((msg = instreamreader.readLine()) != null) {
				
				setLastservermsg(msg);
				
				if (msg.equals("close")) //TODO Why can't I use the Constant here?
					closeConnection();
				
				if (!msg.equals(cmd))
					throw new ServerConnectionException(
							"Server echoed wrong msg: " + msg, null);
			} else
				throw new SocketTimeoutException();
		} catch (SocketTimeoutException e) {
			closeConnection();
			setLastservermsg("Timeout"); //Not really from server
			throw new ServerConnectionException("Timeout waiting for echo", e);
		} catch (IOException e) {
			throw new ServerConnectionException(
					"Error reading server msg", e);
		}

	}
	
	/**
	 * Disconnects the connection to the server.
	 */
	public void disconnect(){
		if (getConnectionState() != ServerConnectionState.CONNECTED
				&& outstream != null)
			// Send command
			try {
				outstream.writeBytes(Commands.CLOSE_CONNECTION);
			} catch (IOException e) {
				// do nothing
			}
		closeConnection();
	}
	
	/**
	 * Tries to create a client socket connected to the server. Will also
	 * create outstream and instream objects for handling input and output.
	 * 
	 * @throws ServerConnectionException
	 */
	public void connect() throws ServerConnectionException
	{
		setConnectionState(ServerConnectionState.CONNECTING);
		
		//Now we try and get the server
		try {
			InetSocketAddress serversock_addr = new InetSocketAddress(this.config.getServeraddr(), this.config.getServerport());
			clientsock = new Socket();
			clientsock.connect(serversock_addr, 1000); //A second to make the connection
			clientsock.setSoTimeout(50);
			if (!clientsock.getTcpNoDelay()) clientsock.setTcpNoDelay(true);
			outstream = new DataOutputStream(clientsock.getOutputStream());
			instream = new DataInputStream(clientsock.getInputStream());
			instreamreader = new BufferedReader(new InputStreamReader(instream));			
			setConnectionState(ServerConnectionState.CONNECTED);

		} catch (UnknownHostException e) {
			setConnectionState(ServerConnectionState.DISCONNECTED);
			//raise something specific to our application
			throw new ServerConnectionException("Unknown Host", e);
		} catch (SocketTimeoutException e) {
			setConnectionState(ServerConnectionState.DISCONNECTED);
			throw new ServerConnectionException("Timed out connecting to server", e);
		} catch (IOException e) {
			setConnectionState(ServerConnectionState.DISCONNECTED);
			throw new ServerConnectionException("Error connecting to server", e);
		}
	}
	
	
	/**
	 * @return The last message recieved from the server
	 */
	public synchronized String getLastservermsg() {
		return this.lastservermsg;
	}
	
	/**
	 * @return The current state of the connection
	 */
	public synchronized ServerConnectionState getConnectionState() {
		return this.status;
	}

	
	//Private Methods
	//----------------------------------------------

	/**
	 * Set the value of the last server message
	 * @param msg
	 */
	public synchronized void setLastservermsg(String msg) {
		this.lastservermsg = msg;
	}
	
	/**
	 * Set the state variable.
	 * @param state
	 */
	public synchronized void setConnectionState(ServerConnectionState state) {
		this.status = state;
	}
	
	/**
	 * Shutdown the connection to the server. Closing all sockets
	 * and streams.
	 */
	private void closeConnection()
	{
		try {
			if (instreamreader != null) instreamreader.close();
			if (instream != null) instream.close();
			if (outstream != null) outstream.close();
			if (clientsock != null) clientsock.close();
		} catch (IOException e) {
			//do nothing
		} finally {
			instreamreader = null;
			instream = null;
			outstream = null;
			clientsock = null;
			setConnectionState(ServerConnectionState.DISCONNECTED);
		}
	}
}
